using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;

namespace QAToolsAddin.Components {

    partial class ExcelRefEdit : UserControl {
        public enum XlSelection {
            SingleCell = 1,    //Only one cell can be selected
            SingleArea = 2,    //Only one area can be selected
            MultiArea = 4,     //No restriction on the selection
        }

        public enum XlScope {
            Worksheet = 1,   //Selection is bonded to the worksheet
            Workbook = 2,    //Selection is bonded to all worksheets in the workbook
            Application = 4, //Selection is bonded to all workbooks in the application
        }

        public enum XlMode {
            ValueText = 1,          //Display joined values at the selected address or typed text
            ValueNumber = 2,        //Numeric value
            AddressRelative = 3,    //Display the address relative to the current sheet
            AddressRange = 4,       //Display the range address only
            AddressWorksheet = 5,   //Display the range address with the sheet name
            AddressWorkbook = 6     //Display the range address with the sheet name and workbook name
        }


        protected enum XlWinState { Minimized, Maximized }

        private Form _form;
        private Excel.Application _excelapp;
        private Excel.Workbook _initialWorkbook;
        private Excel.Worksheet _initialWorksheet;
        private Excel.Workbook _curWb;
        private string _curWbName = string.Empty;
        private Excel.Worksheet _curWs;
        private string _curWsName = string.Empty;
        private Excel.Range _curRange;
        private string _curRangeName = string.Empty;
        private RefEditProperties _properties;
        private XlWinState _formstate = XlWinState.Maximized;
        private XlMode _displaymode = XlMode.AddressRange;
        private bool _disableEvents;

        public ExcelRefEdit() {
            InitializeComponent();
            DisplayMode = XlMode.AddressRelative;
            Delimiter = ";";
            AllowTextInput = false;
            ReadOnly = false;
            MultiSelect = XlSelection.MultiArea;
            Scope = XlScope.Workbook;
        }

        private void RefEdit_Load(object sender, EventArgs e) {
            if (((ExcelForm)ParentForm).ExcelApp != null) {
                _form = ParentForm;
                _excelapp = ((ExcelForm)ParentForm).ExcelApp;
                _initialWorkbook = _excelapp.ActiveWorkbook;
                _initialWorksheet = (Excel.Worksheet)_excelapp.ActiveSheet;
            }
            if (ParentForm is ExcelForm == false)
                throw new ArgumentException("An ExcelForm parent type is required.");
            //ctrlTextBoxCombo.Validating += Validating;
        }

        public event EventHandler RangeChanged;
        public event EventHandler KeyEscape;
        public event EventHandler KeyReturn;
        public event Excel.AppEvents_WindowActivateEventHandler WindowActivate;
        //public event CancelEventHandler Validating;

        public string NumberFormat {
            get { return ctrlTextBoxCombo.NumberFormat; }
            set { ctrlTextBoxCombo.NumberFormat = value; }
        }

        public double MinNumber {
            get { return ctrlTextBoxCombo.MinNumber; }
            set { ctrlTextBoxCombo.MinNumber = value; }
        }

        public double MaxNumber {
            get { return ctrlTextBoxCombo.MaxNumber; }
            set { ctrlTextBoxCombo.MaxNumber = value; }
        }

        [DefaultValue(XlMode.AddressRelative)]
        public XlMode DisplayMode {
            get { return _displaymode; }
            set {
                _displaymode = value;
                ctrlTextBoxCombo.IsNumeric = _displaymode == XlMode.ValueNumber;
            }
        }

        [DefaultValue(";")]
        public string Delimiter { get; set; }

        [DefaultValue(false)]
        public bool AllowTextInput { get; set; }

        [DefaultValue(false)]
        public bool ReadOnly {
            get { return ctrlTextBoxCombo.ReadOnly; }
            set {
                ctrlTextBoxCombo.ReadOnly = value;
                btSelectRange.Enabled = !value;
            }
        }

        [DefaultValue(XlSelection.MultiArea)]
        public XlSelection MultiSelect { get; set; }

        [DefaultValue(XlScope.Workbook)]
        public XlScope Scope { get; set; }

        [DefaultValue(null)]
        public ExcelRefEdit LinkedRef { get; set; }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string WorkbookName {
            get {
                return _curWbName;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string WorksheetName {
            get {
                return _curWsName;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string RangeName {
            get {
                return _curRangeName;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Excel.Workbook Workbook {
            get {
                return _curWb;
            }
            set {
                SetWorkbook(value, true);
                RefreshDisplay();
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Excel.Worksheet Worksheet {
            get {
                return _curWs;
            }
            set {
                SetWorksheet(value, true);
                RefreshDisplay();
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Excel.Range Range {
            get {
                return _curRange;
            }
            set {
                SetRange(value);
                RefreshDisplay();
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsEmpty {
            get {
                return string.IsNullOrEmpty(ctrlTextBoxCombo.Text);
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsRange {
            get { return _curRange != null; }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public object Value {
            get {
                if (DisplayMode == XlMode.ValueText)
                    return ctrlTextBoxCombo.Text;
                if (DisplayMode == XlMode.ValueNumber)
                    return ctrlTextBoxCombo.Value;
                return _curRange;
            }
            set {
                if (DisplayMode == XlMode.ValueText) {
                    if (value is string == false)
                        throw new ArgumentException("Invalide type. A string is expected as DisplayMode is set to ValueText.");
                    ctrlTextBoxCombo.Text = (string)value;
                } else if (DisplayMode == XlMode.ValueNumber) {
                    if (value != null && (value is double == false))
                        throw new ArgumentException("Invalide type. A double is expected as DisplayMode is set to ValueNumber.");
                    ctrlTextBoxCombo.Value = (double?)value;
                } else {
                    if (value is Excel.Range == false)
                        throw new ArgumentException("Invalide type. A range is expected as DisplayMode is set to Address.");
                    SetRange((Excel.Range)value);
                }
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public object[,] Values {
            get {
                if (_curRange != null)
                    return ExcelArray<object>.Get(_curRange.Value);
                if (AllowTextInput)
                    return ExcelArray<object>.Get(ctrlTextBoxCombo.Text);
                return null;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override string Text {
            get {
                return ctrlTextBoxCombo.Text;
            }
            set {
                ctrlTextBoxCombo.Text = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsNotEmpty {
            get {
                return string.IsNullOrEmpty(ctrlTextBoxCombo.Text) == false;
            }
        }

        public void SelectAll() {
            ctrlTextBoxCombo.SelectionStart = 0;
            ctrlTextBoxCombo.SelectionLength = ctrlTextBoxCombo.Text.Length;
        }

        public void Clear() {
            SetWorkbook(null, true);
            RefreshDisplay();
        }

        public void SwapWorkbook(Excel.Workbook workbook) {
            try {
                if (!string.IsNullOrEmpty(_curWsName) && !string.IsNullOrEmpty(_curRangeName))
                    SetRange(((Excel.Worksheet)workbook.Worksheets[_curWsName]).Range[_curRangeName]);
            } catch {
                SetWorkbook(workbook, true);
            }
            RefreshDisplay();
        }

        public void SwapWorksheet(Excel.Worksheet worksheet) {
            try {
                SetRange(worksheet.Range[_curRangeName]);
            } catch {
                SetWorksheet(worksheet, true);
            }
            RefreshDisplay();
        }

        private bool SetWorkbook(Excel.Workbook wb, bool clearWs) {
            if (_curWb == wb)
                return false;
            if (wb == null) {
                _curWb = null;
                _curWbName = string.Empty;
            } else {
                _curWb = wb;
                _curWbName = _curWb.Name;
                if (WindowActivate != null)
                    WindowActivate(_curWb, _curWb.Windows[1]);
            }
            if (clearWs || wb == null)
                SetWorksheet(null, true);
            if (LinkedRef != null && LinkedRef.Workbook != wb)
                LinkedRef.Workbook = Workbook;
            return true;
        }

        private bool SetWorksheet(Excel.Worksheet ws, bool clearRg) {
            if (_curWs == ws)
                return false;
            if (ws == null) {
                _curWs = null;
                _curWsName = string.Empty;
            } else {
                _curWs = ws;
                _curWsName = ws.Name;
                SetWorkbook((Excel.Workbook)_curWs.Parent, false);
            }
            if (clearRg || ws == null)
                SetRange(null);
            if (LinkedRef != null && LinkedRef.Worksheet != ws)
                LinkedRef.Worksheet = ws;
            return true;
        }

        private bool SetRange(Excel.Range rg) {
            if (_curRange == rg)
                return false;
            if (rg == null) {
                _curRange = null;
                _curRangeName = string.Empty;
            } else {
                if (MultiSelect == XlSelection.SingleCell)
                    rg = (Excel.Range)rg.Cells[1, 1];
                _curRange = MultiSelect == XlSelection.SingleArea ? rg.Areas[1] : rg;
                _curRangeName = _curRange.Address;
                SetWorksheet(rg.Worksheet, false);
            }
            if (RangeChanged != null)
                RangeChanged(this, null);
            return false;
        }

        private void RefreshDisplay() {
            _disableEvents = true;
            if (DisplayMode != XlMode.ValueText && DisplayMode != XlMode.ValueNumber) {
                string text = string.Empty;
                bool displayWbName = (DisplayMode == XlMode.AddressRelative && _initialWorkbook != null && _initialWorkbook != _curWb)
                                        || DisplayMode == XlMode.AddressWorkbook;
                bool diplayWsName = (DisplayMode == XlMode.AddressRelative && _initialWorksheet != null && _initialWorksheet != _curWs)
                                        || DisplayMode == XlMode.AddressWorksheet
                                        || DisplayMode == XlMode.AddressWorkbook;
                if (_curRange != null)
                    text = Utils.GetRangeAddress(_curRange, diplayWsName, displayWbName);
                else if (_curWs != null) {
                    if (diplayWsName) {
                        text = Utils.GetWorksheetAddress(_curWs, displayWbName);
                    } else if (_curWb != null) {
                        if (displayWbName)
                            text = Utils.GetWorkbookAddress(_curWb);
                    }
                }
                ctrlTextBoxCombo.Text = text;
            }
            _disableEvents = false;
        }

        private void SelectCurrentRange() {
            _disableEvents = true;
            try {
                if (_curWb != null) {
                    if (_excelapp.ActiveWorkbook != _curWb)
                        ((Excel._Workbook)_curWb).Activate();
                    if (_curWs != null) {
                        if (_excelapp.ActiveSheet != _curWs)
                            ((Excel._Worksheet)_curWs).Activate();
                        if (_curRange != null && _excelapp.ActiveWindow.RangeSelection != _curRange)
                            _curRange.Select();
                    }
                }
            } catch { }
            _form.Activate();
            Focus();
            _disableEvents = false;
        }

        private void ParseTextAddress() {
            if (DisplayMode != XlMode.ValueText && DisplayMode != XlMode.ValueNumber) {
                string text = ctrlTextBoxCombo.Text;
                string wbname = string.Empty;
                string wsname = string.Empty;
                string rgname = string.Empty;
                int lastExc = text.LastIndexOf('!');
                int firstSb = text.IndexOf('[');
                int lastSb = text.IndexOf(']');
                int firstWsCar = lastSb != -1 ? lastSb + 1 : 0;
                if (firstSb != -1 && lastSb != -1)
                    wbname = text.Substring(firstSb + 1, lastSb - firstSb - 1);
                if (lastExc != -1) {
                    wsname = text.Substring(firstWsCar, lastExc - firstWsCar).Trim('\'');
                    rgname = text.Substring(lastExc + 1);
                }

                //try {
                if (wbname != string.Empty && Scope == XlScope.Application)
                    SetWorkbook(_excelapp.Workbooks[wbname], true);
                if (wsname != string.Empty && Scope != XlScope.Worksheet)
                    SetWorksheet((Excel.Worksheet)_curWb.Worksheets[wsname], true);
                if (rgname != string.Empty)
                    SetRange(_curWs.Range[rgname]);
                //} catch {
                //   SetRange(null);
                //}
            }
        }

        private void RegisterEvents() {
            _excelapp.SheetSelectionChange += ExcelEvent_SheetSelectionChange;
            _excelapp.SheetActivate += ExcelEvent_SheetActivate;
            _excelapp.WindowActivate += ExcelEvent_WindowActivate;
        }

        private void UnregisterEvents() {
            try {
                _excelapp.SheetSelectionChange -= new Excel.AppEvents_SheetSelectionChangeEventHandler(ExcelEvent_SheetSelectionChange);
            } catch { }
            try {
                _excelapp.SheetActivate -= ExcelEvent_SheetActivate;
            } catch { }
            try {
                _excelapp.WindowActivate -= ExcelEvent_WindowActivate;
            } catch { }
        }

        private void RefEdit_Enter(object sender, EventArgs e) {
            if (_disableEvents)
                return;
            SelectCurrentRange();
            RegisterEvents();
        }

        private void RefEdit_Leave(object sender, EventArgs e) {
            if (_disableEvents)
                return;
            UnregisterEvents();
            ParseTextAddress();
            RefreshDisplay();
        }

        private void ExcelEvent_WindowActivate(Excel.Workbook Wb, Excel.Window Wn) {
            if (_disableEvents)
                return;
            if (Scope == XlScope.Application) {
                SetWorkbook(Wb, true);
                RefreshDisplay();
                _form.Activate();
            }
        }

        private void ExcelEvent_SheetActivate(object Sh) {
            if (_disableEvents)
                return;
            if (Scope != XlScope.Worksheet) {
                SetWorksheet((Excel.Worksheet)Sh, true);
                RefreshDisplay();
            }
        }

        private void ExcelEvent_SheetSelectionChange(object Sh, Excel.Range Target) {
            if (_disableEvents)
                return;
            _disableEvents = true;
            bool sameWb = Target.Worksheet.Parent == _curWb;
            bool sameWs = Target.Worksheet == _curWs;
            if (_curWb == null || (sameWb && _curWs == null)
                || Scope == XlScope.Application
                || (Scope == XlScope.Worksheet && sameWb && sameWs)
                || (Scope == XlScope.Workbook && sameWb)) {
                ParseRange(Target);
            }
            _form.Activate();
            _disableEvents = false;
        }

        private void ParseRange(Excel.Range range) {
            if (DisplayMode == XlMode.ValueText) {
                var values = new StringBuilder();
                new CellsOperator(range).ForEachValue(delegate(object value, bool isFirstCol, bool isLastCol) {
                    if (values.Length != 0)
                        values.Append(Delimiter);
                    values.Append(value.ToString());
                });
                ctrlTextBoxCombo.Text = values.ToString();
            } else if (DisplayMode == XlMode.ValueNumber) {
                object firstCellValue = ((Excel.Range)range.Areas[1].Cells[1, 1]).Value;
                if (firstCellValue is double) {
                    ctrlTextBoxCombo.Value = (double)firstCellValue;
                } else if (firstCellValue != null) {
                    double parsedValue;
                    if (double.TryParse(firstCellValue.ToString(), out parsedValue))
                        ctrlTextBoxCombo.Value = parsedValue;
                }
            } else {
                SetRange(range);
                RefreshDisplay();
            }
        }

        private void btSelectRange_Click(object sender, EventArgs e) {
            if (ctrlTextBoxCombo.Focused == false)
                Focus();
            ResizeParentForm(_formstate == XlWinState.Maximized ? XlWinState.Minimized : XlWinState.Maximized);
        }

        private void ctrlAddress_Click(object sender, EventArgs e) {
            if (_disableEvents)
                return;
            SelectCurrentRange();
        }

        private void ctrlAddress_KeyPress(object sender, KeyPressEventArgs e) {
            if (e.KeyChar == (char)Keys.Return || e.KeyChar == (char)Keys.Escape) {
                ParseTextAddress();
                RefreshDisplay();
                SelectCurrentRange();
                if (_formstate == XlWinState.Minimized) {
                    btSelectRange_Click(this, null);
                } else if (KeyEscape != null && e.KeyChar == (char)Keys.Escape) {
                    KeyEscape(this, null);
                }
                if (KeyReturn != null && e.KeyChar == (char)Keys.Return) {
                    KeyReturn(this, null);
                }
            }
        }

        private void FormEvent_ExitMinimized(object sender, FormClosingEventArgs e) {
            ResizeParentForm(XlWinState.Maximized);
            e.Cancel = true;
        }

        private void ResizeParentForm(XlWinState targetState) {
            if (_formstate == targetState) return;
            _disableEvents = true;
            _form.SuspendLayout();
            SuspendLayout();
            if (targetState == XlWinState.Minimized) {
                _properties.FormHeight = _form.Height;
                _properties.FormBorder = _form.FormBorderStyle;
                _properties.FormCancelButon = _form.CancelButton;
                _properties.RefEditLocation = Location;
                _properties.RefEditSize = Size;
                _properties.RefEditAnchor = Anchor;
                _properties.RefEditParent = Parent;
                _properties.FormHidedControls = HideAllFormControls(this);
                btSelectRange.Image = Properties.Resources.RefEdit1;
                if (Parent is Form == false) {
                    _form.Controls.Add(this);
                    _form.ActiveControl = this;
                }
                Anchor = AnchorStyles.None;
                _form.Height = (_form.Height - _form.ClientSize.Height) + 24;
                _form.CancelButton = null;
                Location = new Point { X = 2, Y = 1 };
                Size = new Size { Width = _form.ClientSize.Width - 3, Height = 22 };
                _form.FormBorderStyle = FormBorderStyle.FixedDialog;
                _form.FormClosing += FormEvent_ExitMinimized;
            } else {
                btSelectRange.Image = Properties.Resources.RefEdit0;
                if (_properties.RefEditParent is Form == false) {
                    _properties.RefEditParent.Controls.Add(this);
                }
                foreach (Control ctrl in _properties.FormHidedControls)
                    ctrl.Visible = true;
                _form.ActiveControl = this;
                _form.FormBorderStyle = _properties.FormBorder;
                _form.Height = _properties.FormHeight;
                _form.CancelButton = _properties.FormCancelButon;
                Location = _properties.RefEditLocation;
                Size = _properties.RefEditSize;
                Anchor = _properties.RefEditAnchor;
                _form.FormClosing -= FormEvent_ExitMinimized;
            }
            _formstate = targetState;
            ResumeLayout();
            _form.ResumeLayout();
            _disableEvents = false;
        }

        private List<Control> HideAllFormControls(Control exclude) {
            var hided = new List<Control>();
            Control container = exclude;
            while (true) {
                foreach (Control ctrl in container.Parent.Controls) {
                    if (ctrl != container && ctrl.Visible) {
                        ctrl.Visible = false;
                        hided.Add(ctrl);
                    }
                }
                if (container.Parent is Form) {
                    if (container != exclude && container.Visible) {
                        container.Visible = false;
                        hided.Add(container);
                    }
                    break;
                }
                container = container.Parent;
            }
            return hided;
        }

        private struct RefEditProperties {
            public List<Control> FormHidedControls;
            public FormBorderStyle FormBorder;
            public IButtonControl FormCancelButon;
            public int FormHeight;
            public AnchorStyles RefEditAnchor;
            public Control RefEditParent;
            public Point RefEditLocation;
            public Size RefEditSize;
        }

        private class CustomButton : Button {
            internal CustomButton() {
                SetStyle(ControlStyles.Selectable, false);
            }
        }

    }


}
